package com.health.trans.kit;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.*;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.health.trans.pojo.*;
import com.health.trans.util.CustomRepository;
import com.health.trans.util.DataSourceUtil;
import com.health.trans.util.EntityOperations;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.domain.Specification;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * @author nqlz
 */
@Slf4j
public class SendBase<T, J extends Serializable> {

    protected CtTimeDto ctTimeDto;
    protected final String tableName;
    protected int recursion = 0;
    protected final Class<T> readTableClass;
    protected final TransParam param;
    protected final ReaderJson reader;
    protected final WriterJson writer;
    protected int size;
    protected int targetCount;
    protected long queryCount;
    protected JpaHelper jpaHelper;
    protected BaseSpecific<T> baseSpecific;
    protected final Date initStartDate = new Date();
    protected final List<CtTimeDto> ctTimeDtos = new ArrayList<>();
    protected StringBuilder conditionBuilder;
    protected final TableInfo<T> tableInfo;
    protected String baseSql;
    protected final long optimizeErBai = 2000000;
    protected final long optimizedYiBai = 1000000;
    protected CustomRepository<T, J> repository;
    protected final String optimizedUnderName="created_time";
    protected final String optimizedName="createdTime";

    public SendBase(TransParam param, TableInfo<T> tableInfo) {
        this.reader=param.getReader();
        this.writer=param.getWriter();
        this.param = param;
        this.size = reader.getPageSize();
        this.tableInfo = tableInfo;
        this.tableName = tableInfo.getReadTableName();
        this.readTableClass = tableInfo.getReadTableClass();
        this.jpaHelper = DataSourceUtil.getJpaHelper(reader.getSource());
        this.baseSpecific = new BaseSpecific<>(param, tableInfo, optimizedName);
        this.conditionBuilder = BatchConditionKit.getSqlStr(tableInfo, param);
        initQuerySql();
        initCtTimeDtos();
    }

    /**
     * 初始化 查表 sql
     */
    public void initQuerySql() {
        if (Objects.isNull(readTableClass)) {
            baseSql = "select * from " + tableInfo.getReadTableName() + " ";
        } else {
            baseSql = SqlSentence.getSelectSql(readTableClass);
        }
    }

    public String getTableName() {
        if (Objects.isNull(readTableClass)) {
            return tableInfo.getReadTableName();
        } else {
            return SqlSentence.getTableName(readTableClass);
        }
    }

    public void initCtTimeDtos() {
        log.info("开始初始化查询规则：" + tableName);
        repository = EntityOperations.getRepositoryByClass(readTableClass);
        //暂时不优化查询规则,class不存在或字段名不存在时
        boolean optimized =  Objects.isNull(readTableClass) || (!ReflectUtil.hasField(readTableClass, optimizedUnderName));
        if (optimized) {
            String condition = StrUtil.isNotBlank(conditionBuilder.toString()) ? " where " + conditionBuilder.toString() : "";
            String sql;
            if (Objects.nonNull(readTableClass)) {
                sql = SqlSentence.countQuery(readTableClass, condition);
            } else {
                sql = "select count(1) from " + tableInfo.getReadTableName() + condition;
            }
            queryCount = jpaHelper.count(sql);
            return;
        }

        PageUtil.setOneAsFirstPageNo();

        Specification<T> specification = baseSpecific.init();
        //切换读库
        DynamicDataSourceContextHolder.push(reader.getSource());
        queryCount = repository.count(specification);

        int totalCount = reader.getMaxCount();
        targetCount = (queryCount >= totalCount && totalCount > 0) ? totalCount : (int) queryCount;
        int totalPage = PageUtil.totalPage(targetCount, size);

        log.info(tableName + " 需处理数据总数：" + targetCount + " 处理size:" + size + " 总页数： " + totalPage);
        //如果 同步数量 targetCount 大于 200万，则启动 created_time 计算模式
        //获取最早时间和最晚时间

        if (!ReflectUtil.hasField(readTableClass, optimizedName)) {
            ctTimeDto = null;
            return;
        }
        ctTimeDto = baseSpecific.getFirstCtTime(readTableClass);

        if (targetCount > optimizeErBai) {
            while (ctTimeDto.hasDate()) {
                //计算总量
                long maxCount = getMaxCount(10);
                task(maxCount);
            }
        } else {
            ctTimeDtos.add(ctTimeDto);
        }
        log.info(tableName + " 执行初始化查询规则完毕，执行时间为：" + ThreadMonitor.getTime(initStartDate, new Date()));
    }


    public void task(long maxCount) {
        if (maxCount >= optimizedYiBai && maxCount <= optimizeErBai) {
            saveRules();
        } else if (maxCount > optimizeErBai) {
            long dtoMaxCount = getMaxCount(-1);
            recursion++;
            if (recursion > 1 && dtoMaxCount < optimizedYiBai) {
                saveRules();
            } else if (ctTimeDto.intervalZero()) {
                saveRules();
            } else {
                task(dtoMaxCount);
            }
        } else if (ctTimeDto.intervalZero() || ctTimeDto.gtMax()) {
            saveRules();
        }
    }

    private void saveRules() {
        ctTimeDtos.add(ObjectUtil.clone(ctTimeDto));
        ctTimeDto.setMinDate(ctTimeDto.getTargetDate());
        recursion = 0;
    }

    public long getMaxCount(int addDays) {
        Date endDate = ctTimeDto.addTargetDate(ctTimeDto.getTargetDate(), addDays);
        Date maxDate = ctTimeDto.getMaxDate();
        String sql = "  (" + "" + optimizedUnderName + "" + " >= '"
                + DateUtil.formatDateTime(ctTimeDto.getMinDate()) + "' and  " + optimizedUnderName + " < '"
                + DateUtil.formatDateTime(endDate) + "')";
        String countQuery = SqlSentence.countQuery(readTableClass, BatchConditionKit.appendSql(ObjectUtil.clone(conditionBuilder), sql));
        ctTimeDto.setTargetDate(DateUtil.compare(endDate, maxDate) > 0 ? DateUtil.offsetDay(maxDate, 1) : endDate);
        long count = jpaHelper.count(countQuery);
        ctTimeDto.setCount(count);
        return count;
    }
}
